package com.nowcoder.community.service;

import com.nowcoder.community.dao.elasticsearch.DiscussPostRepository;
import com.nowcoder.community.entity.DiscussPost;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @ClassName ElasticsearchService
 * @Description
 * @Author hhwu
 * @Date 2022/5/30 13:22
 * Version 1.0
 **/
@Service
public class ElasticsearchService {
    /**
     * @Author hhwu
     * @Description //TODO 持久层的操作DiscussPostRepository集成的父类已经给我们封装好了，因此不需要自己写持久层，在服务层可以直接调用即可
     * @Date 13:44 2022/5/30
     **/

    @Autowired
    private DiscussPostRepository discussRepository;

    @Autowired// 为了将查到的目标数据进行封装为高亮显示
    private ElasticsearchTemplate elasticTemplate;

    public void saveDiscussPost(DiscussPost post) {
        /**
         * @Author hhwu
         * @Description //TODO 想es数据库添加一个帖子
         * @Date 13:26 2022/5/30
         * @Param [post]
         * @return void
         **/
        discussRepository.save(post);
    }

    public void deleteDiscussPost(int id) {
        /**
         * @Author hhwu
         * @Description //TODO 通过帖子id删除es数据库中的一个帖子
         * @Date 13:27 2022/5/30
         * @Param [id]
         * @return void
         **/
        discussRepository.deleteById(id);
    }

    public Page<DiscussPost> searchDiscussPost(String keyword, int current, int limit) {
        /**
         * @Author hhwu
         * @Description //TODO 返回一页数据。返回值page是java定义的类型，而非自定义的page，keyword为带查询的关键字，current为页码，limit为每页的数据条数
         * TODO 使用discussRepository进行搜索，得到2份数据，但返回的是没有高亮显示的元素数据，需要通过elasticTemplate进行高亮格式数据的封装
         * @Date 13:30 2022/5/30
         * @Param [keyword, current, limit]
         * @return org.springframework.data.domain.Page<com.nowcoder.community.entity.DiscussPost>
         **/
        SearchQuery searchQuery = new NativeSearchQueryBuilder()// 构造查询对象
                // 查询内容，以及在哪些字段中查询
                .withQuery(QueryBuilders.multiMatchQuery(keyword, "title", "content"))
                // 按照什么顺序显示查询结果
                .withSort(SortBuilders.fieldSort("type").order(SortOrder.DESC))
                .withSort(SortBuilders.fieldSort("score").order(SortOrder.DESC))
                .withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC))
                // 分页设置
                .withPageable(PageRequest.of(current, limit))// current为页码
                // 在哪些字段中查到目标数据时才高亮显示，高亮显示的格式
                .withHighlightFields(
                        new HighlightBuilder.Field("title").preTags("<em>").postTags("</em>"),
                        new HighlightBuilder.Field("content").preTags("<em>").postTags("</em>")
                ).build();

        return elasticTemplate.queryForPage(searchQuery, DiscussPost.class, new SearchResultMapper() {
            // 对数据进行封装，尤其是对高亮数据的封装
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> aClass, Pageable pageable) {
                SearchHits hits = response.getHits();// 获取命中的数据
                if (hits.getTotalHits() <= 0) {// 如果没有击中目标值
                    return null;
                }

                List<DiscussPost> list = new ArrayList<>();
                for (SearchHit hit : hits) {// 将每一条击中的数据记录进行封装
                    DiscussPost post = new DiscussPost();

                    String id = hit.getSourceAsMap().get("id").toString();
                    post.setId(Integer.valueOf(id));

                    String userId = hit.getSourceAsMap().get("userId").toString();
                    post.setUserId(Integer.valueOf(userId));

                    String title = hit.getSourceAsMap().get("title").toString();
                    post.setTitle(title);

                    String content = hit.getSourceAsMap().get("content").toString();
                    post.setContent(content);

                    String status = hit.getSourceAsMap().get("status").toString();
                    post.setStatus(Integer.valueOf(status));

                    String createTime = hit.getSourceAsMap().get("createTime").toString();
                    post.setCreateTime(new Date(Long.valueOf(createTime)));

                    String commentCount = hit.getSourceAsMap().get("commentCount").toString();
                    post.setCommentCount(Integer.valueOf(commentCount));

                    // 处理高亮显示的结果
                    HighlightField titleField = hit.getHighlightFields().get("title");
                    if (titleField != null) {// 如果在title中搜到的目标词不为空
                        // 仅高亮搜到的第一个词即可,并且覆盖之前的title
                        post.setTitle(titleField.getFragments()[0].toString());
                    }

                    HighlightField contentField = hit.getHighlightFields().get("content");
                    if (contentField != null) {
                        post.setContent(contentField.getFragments()[0].toString());
                    }

                    list.add(post);
                }

                return new AggregatedPageImpl(list, pageable,
                        hits.getTotalHits(), response.getAggregations(), response.getScrollId(), hits.getMaxScore());
            }
        });
    }
}
